/*
 * This file is part of ELCube.
 *
 * ELCube is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * ELCube is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with ELCube.  If not, see <https://www.gnu.org/licenses/>.
 */
package cn.nkpro.elcube.docengine.service.impl;

import cn.nkpro.elcube.basic.Constants;
import cn.nkpro.elcube.basic.PageList;
import cn.nkpro.elcube.basic.TransactionSync;
import cn.nkpro.elcube.data.mybatis.pagination.PaginationContext;
import cn.nkpro.elcube.docengine.NkDocEngine;
import cn.nkpro.elcube.docengine.NkDocEngineThreadLocal;
import cn.nkpro.elcube.docengine.NkDocProcessor;
import cn.nkpro.elcube.docengine.gen.DocH;
import cn.nkpro.elcube.docengine.gen.DocHExample;
import cn.nkpro.elcube.docengine.model.DocDefHV;
import cn.nkpro.elcube.docengine.model.DocHPersistent;
import cn.nkpro.elcube.docengine.model.DocHV;
import cn.nkpro.elcube.docengine.model.DocState;
import cn.nkpro.elcube.docengine.model.es.DocHES;
import cn.nkpro.elcube.docengine.service.NkDocEngineFrontService;
import cn.nkpro.elcube.docengine.service.NkDocPermService;
import cn.nkpro.elcube.exception.NkAccessDeniedException;
import cn.nkpro.elcube.exception.NkOperateNotAllowedException;
import cn.nkpro.elcube.security.SecurityUtilz;
import cn.nkpro.elcube.utils.BeanUtilz;
import cn.nkpro.elcube.utils.UUIDHexGenerator;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.IllegalTransactionStateException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.Assert;

import java.util.Date;

@Slf4j
public class NkDocEngineServiceImpl extends AbstractNkDocEngine implements NkDocEngineFrontService {

    @Autowired@SuppressWarnings("all")
    private NkDocFinder docFinder;

    /**
     * 从数据库表中分页查询单据列表
     */
    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public PageList<DocH> list(String docType, int offset, int rows, String orderBy){
        DocHExample example = new DocHExample();
        DocHExample.Criteria criteria = example.createCriteria();

        if(StringUtils.isNotBlank(docType))
            criteria.andDocTypeEqualTo(docType);

        example.setOrderByClause(StringUtils.defaultIfBlank(orderBy,"CREATED_TIME asc"));

        PaginationContext context = PaginationContext.init();

        return new PageList<>(docHMapper.selectByExample(example,new RowBounds(offset,rows)),offset,rows,context.getTotal());
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public DocHV create(String docType, String preDocId, String optSource, Function function) {
        DocHV docHV = createDocHV(docType, preDocId, false);
        if(function!=null)function.apply(docHV);
        return execUpdate(docHV, optSource, null);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public DocHV createIfYes(String docType, String preDocId, String optSource, FunctionCancelable function) {
        DocHV docHV = createDocHV(docType, preDocId, false);
        if(function!=null&&!function.apply(docHV)){
            return null;
        }
        return execUpdate(docHV, optSource, null);
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public DocHV createForView(String docType, String preDocId) {

        // 启用本地线程变量
        NkDocEngineThreadLocal.enableLocalDoc();

        if(log.isInfoEnabled())log.info("创建单据 开始");

        docPermService.assertHasDocPerm(NkDocPermService.MODE_WRITE, docType);

        DocHV docHV = createDocHV(docType, preDocId, true);

        docHV.setRuntimeKey(Constants.CACHE_DOC_RUNTIME+ docHV.getDocId()+":"+UUIDHexGenerator.generate());

        return docToView(docHV);
    }

    @Override
    public DocHV copyForView(String copyFromId) {// 启用本地线程变量
        NkDocEngineThreadLocal.enableLocalDoc();

        if(log.isInfoEnabled())log.info("创建单据 开始");

        DocHV from = detail(copyFromId);

        // 检查查看权限
        docPermService.assertHasDocPerm(NkDocPermService.MODE_READ, from.getDocId(), from.getDocType());
        // 获取前序单据
        DocHV preDoc = null;
        if(StringUtils.isNotBlank(from.getPreDocId()) && !StringUtils.equalsIgnoreCase(from.getPreDocId(),"@")){
            if(NkDocEngineThreadLocal.existUpdated(from.getPreDocId())){
                preDoc = NkDocEngineThreadLocal.getUpdated(from.getPreDocId());
            }else{
                preDoc = detail(from.getPreDocId());
            }
        }

        // 获取单据配置
        DocDefHV def = from.getDef();

        String today = format.format(new Date());
        if(!(StringUtils.compare(def.getValidFrom(),today,true)<=0&&StringUtils.compare(def.getValidTo(),today,false)>=0)) {
            throw new NkOperateNotAllowedException("超出模型有效期限");
        }

        // 验证业务流
        validateFlow(def,preDoc);

        // 获取单据处理器
        NkDocProcessor processor = customObjectManager.getCustomObject(def.getRefObjectType(), NkDocProcessor.class);

        DocHV docHV = processor.toCopy(from, true);
        docHV.setRuntimeKey(Constants.CACHE_DOC_RUNTIME+ docHV.getDocId()+":"+UUIDHexGenerator.generate());

        return docToView(docHV);
    }

    private DocHV createDocHV(String docType, String preDocId, boolean isView){
        // 获取前序单据
        DocHV preDoc = null;
        if(StringUtils.isNotBlank(preDocId) && !StringUtils.equalsIgnoreCase(preDocId,"@")){
            if(NkDocEngineThreadLocal.existUpdated(preDocId)){
                preDoc = NkDocEngineThreadLocal.getUpdated(preDocId);
            }else{
                preDoc = detail(preDocId);
            }
        }
        // 获取单据配置
        DocDefHV def = docDefService.getDocDefForRuntime(docType);

        String today = format.format(new Date());
        if(!(StringUtils.compare(def.getValidFrom(),today,true)<=0&&StringUtils.compare(def.getValidTo(),today,false)>=0)) {
            throw new NkOperateNotAllowedException("超出模型有效期限");
        }


        // 验证业务流
        validateFlow(def,preDoc);

        // 获取单据处理器
        NkDocProcessor processor = customObjectManager.getCustomObject(def.getRefObjectType(), NkDocProcessor.class);

        // 创建单据
        return processor.toCreate(def, preDoc, isView);
    }


    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public DocState state(String docId) {

        DocHPersistent docHPersistent = fetchDoc(docId);

        return BeanUtilz.copyFromObject(docHPersistent, DocState.class);
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public DocHV detailView(String docId, boolean edit) {

        // 启用本地线程变量
        NkDocEngineThreadLocal.enableLocalDoc();

        final long start = System.currentTimeMillis();

        try{

            if(log.isInfoEnabled()){
                log.info("获取单据视图:"+docId);
            }

            // 先获取单据的持久化数据，主要为了获取模型，来判断权限
            DocHPersistent docHPersistent = fetchDoc(docId);

            if(docHPersistent==null)
                return null;

            // 检查READ权限
            if(!debugContextManager.isDebug()) {
                // 检查单据权限
                if(!docPermService.hasDocPerm(NkDocPermService.MODE_READ, docId, docHPersistent.getDocType())){
                    // 检查用户是否被单据工作流指派
                    if(!(StringUtils.isNotBlank(docHPersistent.getProcessInstanceId())&&bpmTaskService.isDocAssignee(docId, SecurityUtilz.getUser().getId()))){
                        throw new NkAccessDeniedException(String.format("没有单据[%s:%s]-查看的访问权限", docHPersistent.getDocType(), docId));
                    }
                }
            }

            // 尝试先从本地线程中获取单据对象
            // 因为本地单据clone后会导致数据反序列化找不到脚本编译的Class，所以暂时放弃这个方案
            //DocHV docHV = NkDocEngineContext.getDoc(
            //    docId,
            //    (id)-> persistentToHV(docHPersistent)
            //);

            return docToView(persistentToHV(docHPersistent,edit,true));

        }finally {

            if(log.isInfoEnabled()){
                log.info("获取单据视图 完成 总耗时{}ms",System.currentTimeMillis() - start);
            }
        }
    }

    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public DocHV detail(String docType, String businessKey) {
        String docId = getDocId(docType,businessKey);
        if(docId!=null){
            return detail(docId);
        }
        return null;
    }

    @Override
    public NkDocFinder find(String... docType) {
        return docFinder.createFinder(docType);
    }


    /**
     *
     * @throws IllegalTransactionStateException 在只读transaction状态下执行；
     */
    @Override
    public DocHV calculate(DocHV doc, String fromCard, Object options) {

        if(log.isInfoEnabled())log.info("开始单据计算:"+doc.getDocId());
        try{

            validate(doc);
            //DocDefHV defHV = docDefService.deserializeDef(doc.getDef());

            // 获取单据处理器 并执行
            doc =  customObjectManager
                    .getCustomObject(doc.getDef().getRefObjectType(), NkDocProcessor.class)
                    .calculate(doc, fromCard, options);

            //doc.setDef(defHV);

            return doc;

        }finally {
            if(log.isInfoEnabled())log.info("单据计算 完成");
        }
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
    public DocHV calculateForView(DocHV doc, String fromCard, Object options) {
        if(log.isInfoEnabled())log.info("开始单据计算:"+doc.getDocId());
        try{

            validate(doc);
            //DocDefHV defHV = docDefService.deserializeDef(doc.getDef());

            // 前端提交的数据不能确保有配置信息，所以获取单据配置，并填充
            runtimeAppend(doc);

            // 获取单据处理器 并执行
            doc =  customObjectManager
                    .getCustomObject(doc.getDef().getRefObjectType(), NkDocProcessor.class)
                    .calculate(doc, fromCard, options);

            //doc.setDef(defHV);

            return docToView(doc);

        }finally {
            if(log.isInfoEnabled())log.info("单据计算 完成");
        }
    }

    /**
     *
     * @throws IllegalTransactionStateException 在无transaction状态下执行；如果当前已有transaction，则抛出异常
     */
    @Override
    @Transactional(propagation = Propagation.NEVER)
    public Object call(DocHV doc, String fromCard, String method, Object options) {
        if(log.isInfoEnabled())log.info("开始调用单据程序:"+doc.getDocId());
        try{
            validate(doc);

            // 前端提交的数据不能确保有配置信息，所以获取单据配置，并填充
            DocDefHV def = docDefService.deserializeDef(doc.getDef());

            // 获取单据处理器 并执行
            return customObjectManager
                    .getCustomObject(def.getRefObjectType(), NkDocProcessor.class)
                    .call(doc, fromCard, method, options);
        }finally {
            if(log.isInfoEnabled())log.info("调用单据程序 完成");
        }
    }

    @Override
    @Transactional
    public DocHV doUpdateView(DocHV docHV, String optSource, UpdateConfig config){

        if(debugContextManager.isDebug()){
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            System.out.println("\n\n\n");
            System.out.println("当前请求为Debug模式，所有操作的事务不会提交，但对于外部系统接口、redis、es等可能会有不可避免的数据更新，需要谨慎");
            System.out.println("\n\n\n");
        }


        if (log.isInfoEnabled())
            log.info("开始保存单据视图:"+docHV.getDocId());

        docPermService.assertHasDocPerm(NkDocPermService.MODE_WRITE, docHV.getDocType());


        if (log.isInfoEnabled()){
            log.info(StringUtils.EMPTY);
            log.info("1.第一次获取原始单据 用于填充被权限过滤的数据");
        }
        DocHV runtime = runtimeAppend(docHV);


        if (log.isInfoEnabled()){
            log.info(StringUtils.EMPTY);
            log.info("2.反序列化配置");
        }
        docHV.setDef(runtime.getDef());

        // 如果单据为修改模式下，检查是否有该单据的write权限
        if (!runtime.isNewCreate()) {
            if (!debugContextManager.isDebug()) {
                docPermService.assertHasDocPerm(NkDocPermService.MODE_WRITE, docHV.getDocId(), docHV.getDocType());
            }
        }

        if (log.isInfoEnabled()){
            log.info(StringUtils.EMPTY);
            log.info("3.执行更新逻辑");
        }

        try{
            NkDocEngineThreadLocal.threadLocalUpdateConfig.set(config);
            docHV = execUpdate(docHV, optSource, null);
        }finally {
            NkDocEngineThreadLocal.threadLocalUpdateConfig.remove();
        }

        if (log.isInfoEnabled()){
            log.info(StringUtils.EMPTY);
            log.info("4.将单据数据做界面显示处理");
        }
        docToView(docHV);

        if (debugContextManager.isDebug()) {
            debugContextManager.addDebugResource("$" + docHV.getDocId(), docHV.toPersistent());
        }

        if (log.isInfoEnabled()){
            log.info(StringUtils.EMPTY);
            log.info("5.更新后清理过程引用");
        }
        runtimeClear(docHV);
        docHV.clearItemContent();
        return docHV;
    }

    @Override
    @Transactional
    public DocHV doUpdate(String docId, String optSource, NkDocEngine.Function function){
        return lockDocDo(docId,(id)->{
            DocHV doc = detailForUpdate(docId);
            Assert.notNull(doc,"单据没有找到:"+docId);
            if(function!=null)function.apply(doc);
            doc = execUpdate(doc, optSource, null);
            doc.clearItemContent();
            return doc;
        });
    }

    @Override
    @Transactional
    public DocHV doUpdate(String docType, String businessKey, String optSource, NkDocEngine.Function function){
        String id = getDocId(docType,businessKey);
        Assert.notNull(id,"单据没有找到:"+docType+" "+businessKey);
        return doUpdate(id, optSource,function);
    }

    @Override
    @Transactional
    public DocHV doUpdateAgain(String docId, String optSource, NkDocEngine.Function function){

        DocHV doc = NkDocEngineThreadLocal.getUpdated(docId);

        doc =  customObjectManager
                .getCustomObject(doc.getDef().getRefObjectType(), NkDocProcessor.class)
                .clone(doc);

        if(function!=null)function.apply(doc);
        doc = execUpdate(doc, optSource, null);
        doc.clearItemContent();
        return doc;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public DocHV random(DocHV doc) {
        if(log.isInfoEnabled())log.info("开始生成随机数据");
        try{

            validate(doc);
            DocDefHV def = docDefService.deserializeDef(doc.getDef());

            // 获取单据处理器 并执行
            return customObjectManager
                    .getCustomObject(def.getRefObjectType(), NkDocProcessor.class)
                    .random(doc);
        }finally {
            if(log.isInfoEnabled())log.info("生成随机数据 完成");
        }
    }


    @Transactional
    @Override
    public void onBpmKilled(String docId, String processKey, String optSource) {
        lockDocDo(docId,(id)->{
            DocHV docHV = detail(docId);
            customObjectManager.getCustomObject(docHV.getDef().getRefObjectType(), NkDocProcessor.class)
                    .doOnBpmKilled(docHV, processKey, optSource);
            // 事务提交后清空缓存
            TransactionSync.runAfterCommit("清除单据缓存"+docId, ()-> {
                redisSupport.deleteHash(Constants.CACHE_DOC, docId);
                redisSupport.delete(Constants.CACHE_DOC_INDEPENDENT+docId);
            });
            return docHV;
        });
    }


    /**
     * 重建索引，单据没有修改，所以两个doc是一样的
     */
    @Transactional
    @Override
    public void reDataSync(DocHV doc){
        customObjectManager.getCustomObject(doc.getDef().getRefObjectType(), NkDocProcessor.class)
                .doUpdateIndex(doc, null, true);
        this.dataSync(doc, doc, true);
        searchEngine.indexBeforeCommit(DocHES.from(doc));
    }

    /**
     *
     * 因为前端回传的单据配置是经过权限过滤的，不完整的，
     * 所以需要重新获取完整的单据和数据进行处理
     * 涉及方法：
     * create(String, String, String, NkDocEngine.Function)
     * calculate(DocHV, String, Object)
     * doUpdateView(DocHV, String)
     *
     * 逻辑处理完成后，再进行权限过滤，返回给前端
     * @see #docToView(DocHV)
     *
     */
    private DocHV runtimeAppend(DocHV doc){

        // 获取单据运行时状态，如果runtimeKey为空，获取当前最新单据作为运行时
        DocHV docHVRuntime;
        if(StringUtils.isNotBlank(doc.getRuntimeKey())){
            docHVRuntime = redisRuntime.get(doc.getRuntimeKey());

            Assert.notNull(docHVRuntime,"操作已超时");
        }else{
            docHVRuntime = detail(doc.getDocId());
            Assert.notNull(docHVRuntime,"原始单据不存在");

            doc.setRuntimeKey(Constants.CACHE_DOC_RUNTIME+ doc.getDocId()+":"+ UUIDHexGenerator.generate());
        }

        doc.setDef(docHVRuntime.getDef());
        // 将运行时数据填充到用户单据中，使单据数据完整
        docHVRuntime.getData().forEach((k,v)-> doc.getData().putIfAbsent(k,v));
        // docHVRuntime.getDef().getCards().forEach(runtimeCard->{
        //     boolean present = doc.getDef().getCards().stream()
        //             .anyMatch(card -> StringUtils.equals(card.getCardKey(), runtimeCard.getCardKey()));
        //     if(!present)
        //         doc.getDef().getCards().add(runtimeCard);
        // });
        // doc.getDef().getCards().sort(Comparator.comparingInt(DocDefI::getOrderBy));

        return docHVRuntime;
    }
}
